package com.atguigu.gulimall.search.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.exception.GulimallEnum;
import com.atguigu.common.exception.GulimallException;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.search.config.GulimallElasticSearchConfig;
import com.atguigu.gulimall.search.es.EsContant;
import com.atguigu.gulimall.search.es.SkuEsModel;
import com.atguigu.gulimall.search.feign.ProductServiceFeign;
import com.atguigu.gulimall.search.service.MallSearchService;
import com.atguigu.gulimall.search.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.TotalHits;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

@Slf4j
@Service
public class MallSearchServiceImpl implements MallSearchService {
    @Autowired
    private RestHighLevelClient client;
    @Autowired
    private ProductServiceFeign productServiceFeign;
    @Override
    public SearchResult search(SearchParam searchParam) {
         //CompletableFuture
        //构建请求的搜索条件
        SearchSourceBuilder builder =buildSearchRequestByParam(searchParam);
        //创建一个搜索请求，其中封装了搜索条件
        SearchRequest request = new SearchRequest(new String[]{EsContant.ES_INDEX_PRODUCT},builder);
        SearchResult searchResult =null;
        try {
            //执行搜索
            SearchResponse response= client.search(request, GulimallElasticSearchConfig.COMMON_OPTIONS);
            //把搜索结果解析成前端需要的格式SearchResult
            searchResult = parseResponseForSearchResulut(response,searchParam);
        } catch (IOException e) {
            log.error("es聚合搜索失败~",e);
            throw new GulimallException(GulimallEnum.EXCUTE_SEARCH_ERROR);
        }
            return searchResult;
    }

    /**
     * 解析es的返回对象SearchResponse到searchResult中
     * @param response
     * @return
     */
    private SearchResult parseResponseForSearchResulut(SearchResponse response,SearchParam searchParam) {
        SearchHit[] hits = response.getHits().getHits();
        SearchResult searchResult = new SearchResult();
        //封装product
        if (hits !=null && hits.length>0){
            //证明查到了数据
            List<SkuEsModel> product = new ArrayList<>();
            for (SearchHit hit : hits) {
                //这是个json格式的字符串
                String s = hit.getSourceAsString();
                SkuEsModel skuEsModel = JSON.parseObject(s, new TypeReference<SkuEsModel>() {});
                if (StringUtils.isNotEmpty(searchParam.getKeyword())){
                    String skuTitle = hit.getHighlightFields().get("skuTitle").fragments()[0].string();
                    skuEsModel.setSkuTitle(skuTitle);
                }
                product.add(skuEsModel);
            }
            searchResult.setProducts(product);
        }
        //当前页码
        searchResult.setPageNum(searchParam.getPageNum());
        //总记录数
        long value = response.getHits().getTotalHits().value;
        searchResult.setTotal(value);
        //总页码

        long totalPages = value % EsContant.PRODUCT_PAGE_LIMIT == 0 ?
                (value / EsContant.PRODUCT_PAGE_LIMIT) : (value / EsContant.PRODUCT_PAGE_LIMIT + 1);
        searchResult.setTotalPages((int)totalPages);
        // pageNavs
       List<Integer> pageNavs = new ArrayList<>();
        for (int i=1;i<=totalPages;i++){
            pageNavs.add(i);
        }
        searchResult.setPageNavs(pageNavs);
        //解析response里面的聚合brand_agg
        ParsedLongTerms brand_agg = (ParsedLongTerms)response.getAggregations().get("brand_agg");
        //brand_agg.getBuckets(); 表示拿到这种定义聚合下的实际分组 Buckets 就是分组
        List<? extends Terms.Bucket> brandAggBucketsbuckets = brand_agg.getBuckets();
        List<BrandVo> brandVoList = new ArrayList<>();
        for (Terms.Bucket bucket : brandAggBucketsbuckets) {
           Long brandId=(Long)bucket.getKey();
           //这里也就是在同一个品牌下再分组 只不过按 品牌名分组 只会得到一个子分组
           ParsedStringTerms brandNameagg =(ParsedStringTerms)bucket.getAggregations().get("brand_name");
           Terms.Bucket bucket1 = brandNameagg.getBuckets().get(0);
           String brandName=(String)bucket1.getKey();
           ParsedStringTerms brandImgagg =(ParsedStringTerms)bucket.getAggregations().get("brand_img");
           Terms.Bucket bucket2 = brandImgagg.getBuckets().get(0);
           String brandImg=(String)bucket2.getKey();
           BrandVo brandVo = new BrandVo();
           brandVo.setBrandId(brandId);
           brandVo.setBrandName(brandName);
           brandVo.setBrandImg(brandImg);
           brandVoList.add(brandVo);
        }
        searchResult.setBrands(brandVoList);
        //解析response里面的聚合catagory_agg
        //该聚合是按照catalogid进行的 也就是按照catalogId分组
        ParsedLongTerms  catagory_agg= (ParsedLongTerms)response.getAggregations().get("catagory_agg");
        //拿到该聚合下的实际分组
        List<? extends Terms.Bucket> catagoryAggBuckets = catagory_agg.getBuckets();
        List<CatalogVo> catalogVoList = new ArrayList<>();
        for (Terms.Bucket bucket : catagoryAggBuckets) {
            Long catalogId= (Long) bucket.getKey();
            ParsedStringTerms catalogNameAgg = (ParsedStringTerms)bucket.getAggregations().get("catalog_name");
            //因为同一个分类下 在通过name来子分组 也就只能分出一组
            Terms.Bucket catalogNameBucket = catalogNameAgg.getBuckets().get(0);
            String catalogName=(String)catalogNameBucket.getKey();
            CatalogVo catalogVo = new CatalogVo();
            catalogVo.setCatalogId(catalogId);
            catalogVo.setCatalogName(catalogName);
            catalogVoList.add(catalogVo);
        }
        searchResult.setCatalogs(catalogVoList);
        //解析response里面的聚合attrs_agg
        ParsedNested attrs_agg=(ParsedNested)response.getAggregations().get("attrs_agg");
        ParsedLongTerms attr_id=(ParsedLongTerms)attrs_agg.getAggregations().get("attr_id");
        List<? extends Terms.Bucket> attrIdBuckets = attr_id.getBuckets();
        List<AttrVo> attrs = new ArrayList<>();
        for (Terms.Bucket bucket : attrIdBuckets) {
            //这添加的一个功能
//            String queryString = searchParam.get_queryString();
//            if (queryString !=null && queryString.contains("&")){
//                String[] split = queryString.split("&");
//                String s = split[split.length - 1];
//                String[] split1 = s.split("_");
//                String s1 = split1[0];
//                if ((Long) bucket.getKey()==Long.parseLong(s1)){
//                    continue;
//                }
//            }
            //上面是添加的一个功能
            Long attrId=(Long) bucket.getKey();
            ParsedStringTerms attr_name=(ParsedStringTerms)bucket.getAggregations().get("attr_name");
            Terms.Bucket attrNameBucket = attr_name.getBuckets().get(0);
            String attrName= (String) attrNameBucket.getKey();
            ParsedStringTerms attr_value=(ParsedStringTerms)bucket.getAggregations().get("attr_value");
            List<? extends Terms.Bucket> attrValueBuckets = attr_value.getBuckets();
            List<String> attrValueList = new ArrayList<>();
            for (Terms.Bucket attrValueBucket : attrValueBuckets) {
                String attrValue= (String) attrValueBucket.getKey();
                attrValueList.add(attrValue);
            }
            AttrVo attrVo = new AttrVo();
            attrVo.setAttrId(attrId);
            attrVo.setAttrName(attrName);
            attrVo.setAttrValue(attrValueList);
            attrs.add(attrVo);
        }
        searchResult.setAttrs(attrs);
        //封装 private List<NavVo> navs;
        List<String> stringAttrs = searchParam.getAttrs();
        if (stringAttrs !=null && stringAttrs.size()>0){
            //证明前端有属性查询条件
            List<NavVo> navVoList = new ArrayList<>();
            for (String attr : stringAttrs) {
                String[] s = attr.split("_");
                NavVo navVo = new NavVo();
                R info = productServiceFeign.info(Long.parseLong(s[0]));
                if (info.getCode()==0){
                    //typeReference就代表AttrResVo类型
                    TypeReference<AttrResVo> typeReference = new TypeReference<AttrResVo>() {};
                    AttrResVo attrResVo = info.getDataByName(typeReference, "attr");
                    navVo.setNavName(attrResVo.getAttrName());
                }else {
                    navVo.setNavName(s[0]);
                }
                navVo.setNavValue(s[1]);
                String url =null;
                //拿到所有的查询条件，去掉当前
                try {
                    url=URLEncoder.encode(attr, "UTF-8").replace("+", "%20");
                } catch (UnsupportedEncodingException e) {
                    throw new RuntimeException(e);
                }
                navVo.setLink(url);
                navVoList.add(navVo);
            }
            searchResult.setNavs(navVoList);
        }
        return searchResult;
    }
    /**
     * 把前端传来的查询条件进行解析，封装到searchSourceBuilder里面，然后到es里面查数据
     * @param searchParam
     * @return
     */
    private SearchSourceBuilder buildSearchRequestByParam(SearchParam searchParam) {
        //它是个最大的builder
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //构建bool-query的一种builder
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //skuTitle的模糊匹配
        if (StringUtils.isNotEmpty(searchParam.getKeyword())){
            MatchQueryBuilder skuTitleQuery = QueryBuilders.matchQuery("skuTitle",
                    searchParam.getKeyword());
            //must()用户构建must
            boolQuery.must(skuTitleQuery);
        }
        if (searchParam.getCatalog3Id()!=null){
            TermQueryBuilder catalogIdQuery = QueryBuilders.termQuery("catalogId",
                    searchParam.getCatalog3Id());
            //filter用于构建filter
            boolQuery.filter(catalogIdQuery);
        }
        if (searchParam.getBrandId()!=null && searchParam.getBrandId().size()>0){
            TermsQueryBuilder brandIdQuery = QueryBuilders.termsQuery("brandId",
                    searchParam.getBrandId());
            boolQuery.filter(brandIdQuery);
        }
        if (searchParam.getAttrs() !=null&&searchParam.getAttrs().size()>0){
            BoolQueryBuilder boolQueryOfNested = QueryBuilders.boolQuery();
            //&attrs=1_5寸:8寸&attrs=2_ 16G:8G
            List<String> attrs = searchParam.getAttrs();
            for (String attr : attrs) {
                String[] id_attrValues = attr.split("_");
                boolQueryOfNested.must( QueryBuilders.termQuery("attrs.attrId",id_attrValues[0]));
                String[] attrValues = id_attrValues[1].split(":");
                boolQueryOfNested.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
                //多个nested是且的关系，一个nested对应一个attr,也就是搜索的时候
                //一个sku必须都要有这些attr，而且值也只能是attrValues其中的
                NestedQueryBuilder attrsNestedQuery = QueryBuilders.nestedQuery("attrs",boolQueryOfNested, ScoreMode.None);
                boolQuery.filter(attrsNestedQuery);
            }
        }
        if (searchParam.getHasStock() !=null){
            TermQueryBuilder hasStockQuery= QueryBuilders.termQuery("hasStock",
                    searchParam.getHasStock()==1?true:false);
            boolQuery.filter(hasStockQuery);
        }
        if (StringUtils.isNotEmpty(searchParam.getSkuPrice())){
            String skuPrice = searchParam.getSkuPrice();
            String[] split = skuPrice.split("_");
            //不管是_1000 还是 1000_ split函数后 数组长度都会是2
            List<String> filterOfEmpty = new ArrayList<>();
            for (String s : split) {
                if (StringUtils.isNotEmpty(s)){
                    filterOfEmpty.add(s);
                }
            }
            split=filterOfEmpty.toArray(new String[0]);
            RangeQueryBuilder skuPriceQuery = QueryBuilders.rangeQuery("skuPrice");
            if (split.length==2){
                //1_500 该形式
                Integer gte=Integer.parseInt(split[0]);
                Integer lte=Integer.parseInt(split[1]);
                skuPriceQuery.gte(gte).lte(lte);
            }
            if (split.length==1&&("_"+split[0]).equals(skuPrice)){
                Integer lte=Integer.parseInt(split[0]);
                skuPriceQuery.lte(lte);
            }
            if (split.length==1&&(split[0]+"_").equals(skuPrice)){
                Integer gte=Integer.parseInt(split[0]);
                skuPriceQuery.gte(gte);
            }
            boolQuery.filter(skuPriceQuery);
        }
        //排序 sort=saleCount_asc/desc
        if (StringUtils.isNotEmpty(searchParam.getSort())){
            String sort = searchParam.getSort();
            String[] attr_order = sort.split("_");
            searchSourceBuilder.sort(attr_order[0],"asc".equalsIgnoreCase(attr_order[1])? SortOrder.ASC:SortOrder.DESC);
        }
        //分页
        Integer from=(searchParam.getPageNum()-1)*EsContant.PRODUCT_PAGE_LIMIT;
        searchSourceBuilder.from(from).size(EsContant.PRODUCT_PAGE_LIMIT);
        //高亮
        if (StringUtils.isNotEmpty(searchParam.getKeyword())){
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle")
                    .preTags("<b style='color:red'>")
                    .postTags("</b>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }
        searchSourceBuilder.query(boolQuery);
        //聚合分析 AggregationBuilders 它build具体哪种聚合
        //聚合的本质就是对筛选出来的数据进行统计 分类 达到前端的某些数据结构的需要
        //terms就是分组
        //对品牌进行聚合
        TermsAggregationBuilder brandAggs = AggregationBuilders.terms("brand_agg");//给该次聚合取个名字
                brandAggs.field("brandId")//针对具体哪个字段进行聚合
                .size(10);//展示的分组个数
                //在同一个品牌之下，按品牌名进行分组，当然其实一个Id就唯一对应一个名字，只不能这里需要一个名字的数据
        brandAggs.subAggregation(AggregationBuilders.terms("brand_name").field("brandName").size(1));
        brandAggs.subAggregation(AggregationBuilders.terms("brand_img").field("brandImg").size(1));
        searchSourceBuilder.aggregation(brandAggs);
        //对分类进行聚合，前端需要筛选出来的doc的分类统计
        TermsAggregationBuilder catalogAggs = AggregationBuilders.terms("catagory_agg").field("catalogId").size(50)
                .subAggregation(AggregationBuilders.terms("catalog_name").field("catalogName").size(1));
        searchSourceBuilder.aggregation(catalogAggs);
        //对筛选出来的属性进行分组
        NestedAggregationBuilder attrsNestedAgg = AggregationBuilders.nested("attrs_agg", "attrs");
        TermsAggregationBuilder attrsNestedAggOfSub = AggregationBuilders.terms("attr_id").field("attrs.attrId").size(50)
                .subAggregation(AggregationBuilders.terms("attr_value").field("attrs.attrValue").size(10))
                .subAggregation(AggregationBuilders.terms("attr_name").field("attrs.attrName").size(1));
        attrsNestedAgg.subAggregation(attrsNestedAggOfSub);
        searchSourceBuilder.aggregation(attrsNestedAgg);
        String s = searchSourceBuilder.toString();
        System.out.println("构建的json:"+s);
        return searchSourceBuilder;
    }
}
